package com.lam.common.components.data;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.lam.common.base.domain.Result;
import com.lam.common.base.page.TableData;
import com.lam.common.components.data.annotation.DataInject;
import com.lam.common.components.data.constant.DataInjectConstant;
import com.lam.common.components.data.model.Auto;
import com.lam.common.components.data.model.DataDeepConfig;
import com.lam.common.components.data.model.DataFeature;
import com.lam.common.components.data.model.DataInjectConfig;
import com.lam.common.components.data.model.DataInjectible;
import com.lam.common.components.data.model.QueryEntry;
import com.lam.common.components.data.model.ResultType;
import com.lam.common.components.data.service.DataInjectService;
import com.lam.common.components.dict.model.DictConvertible;
import com.lam.common.components.redis.RedisUtil;
import com.lam.common.exception.BaseException;
import com.lam.common.exception.BusinessException;
import com.lam.common.utils.BeanUtil;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.DateUtil;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.SpringUtils;
import com.lam.common.utils.StringUtils;
import com.lam.common.utils.TableInfoUtil;
import com.lam.common.utils.sql.SqlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baomidou.mybatisplus.core.metadata.IPage;

public class DataInjectUtil {
	
	private static final Logger logger = LoggerFactory.getLogger(DataInjectUtil.class);
	
	public static boolean isSupportList(Collection<?> list) {
		return CollectionUtil.isNotEmpty(list) && isSupportObject(CollectionUtil.getFirst(list));
	}
	
	public static boolean isSupportObject(Object o) {
		return o != null && o instanceof DataInjectible;
	}
	
	/**
	 * 支持自动转换的数据类型
	 * @param object
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static boolean supportDataType(Object object) {
		
		if (object instanceof Result) {
			object = ((Result) object).getData();
        }
		
		if(isSupportObject(object)) {
			return true;
		}
		
		if(object instanceof IPage && isSupportList(((IPage) object).getRecords())) {
			return true;
		}
		
		if(object instanceof TableData && isSupportList(((TableData) object).getRecords())) {
			return true;
		}
		
		if(object instanceof Collection && isSupportList((Collection<?>)object)) {
			return true;
		}
		
		return false;
	}
	
	public static Object injectJavaObject(Object object) {
		return injectJavaObject(object, DataFeature.create());
	}

	public static Object injectJavaObject(Object object, DataFeature dataFeature) {
		
    	if(object == null && !supportDataType(object)) {
    		return object;
    	}
    	
    	if (object instanceof Result) {
    		injectJavaObject(((Result)object).getData(), dataFeature);
    		return object;
        }
    	
    	if (object instanceof TableData) {
        	injectJavaList(((TableData)object).getRecords(), dataFeature);
        	return object;
        }
    	
    	if (object instanceof IPage) {
        	injectJavaList(((IPage<?>)object).getRecords(), dataFeature);
        	return object;
        }
    	
    	if(object instanceof DictConvertible){
        	return injectSimpleObject(object, dataFeature);
    	}
    	
    	if(object instanceof Collection){
        	return injectJavaList((Collection<?>)object, dataFeature);
        }
    	
    	//其他对象不支持，需要手动调用去转换
    	return object;
    }
	
	public static <T> Collection<T> injectJavaList(Collection<T> list){
		return injectJavaList(list, DataFeature.create());
	}
	
	public static <T> Collection<T> injectJavaList(Collection<T> list, DataFeature dataFeature){
    	if(CollectionUtil.isEmpty(list) || !isSupportList(list)) {
    		return list;
    	}
    	dataFeature = DataFeature.create(dataFeature);
    	if(dataFeature.injectedContains(list)) {
    		return list;
    	}
    	dataFeature.addInjected(list);
    	
    	T object = list.iterator().next();
    	List<DataDeepConfig> deepList = new ArrayList<>();
    	List<DataInjectConfig> injectConfigList = getDataInjectConfig(object.getClass(), dataFeature, deepList);
    	if(CollectionUtil.isEmpty(injectConfigList)) {
    		return list;
    	}
		
		for (T t : list) {
			if(dataFeature.injectedContains(t)) {
	    		continue;
	    	}
			for (DataInjectConfig injectConfig : injectConfigList) {
				invoke(t, injectConfig);
			}
			dataFeature.addInjected(object);
			deepInject(t, deepList, dataFeature);
		}
		return list;
    }
	
	public static Object injectSimpleObject(Object object) {
		return injectSimpleObject(object, DataFeature.create());
	}
	
	public static Object injectSimpleObject(Object object, DataFeature dataFeature) {
		if (object == null || !isSupportObject(object)) {
			return object;
		}
		dataFeature = DataFeature.create(dataFeature);
		if(dataFeature.injectedContains(object)) {
    		return object;
    	}
		dataFeature.addInjected(object);
		
		List<DataDeepConfig> deepList = new ArrayList<>();
		List<DataInjectConfig> injectConfigList = getDataInjectConfig(object.getClass(), dataFeature, deepList);
		if(CollectionUtil.isEmpty(injectConfigList)) {
			return object;
		}

		for (DataInjectConfig injectConfig : injectConfigList) {
			invoke(object, injectConfig);
		}
		deepInject(object, deepList, dataFeature);
		
		return object;
	}
	
	private static void deepInject(Object object, List<DataDeepConfig> deepList, DataFeature dataFeature) {
		if(object == null || CollectionUtil.isEmpty(deepList)) {
			return ;
		}
		
		try {
			for (DataDeepConfig deepConfig : deepList) {
				Object val = deepConfig.getFieldGetter().invoke(object);
				if(val != null) {
					if(val instanceof List) {
						List<?> list = (List<?>)val;
						if(CollectionUtil.isNotEmpty(list)) {
							injectJavaList(list, dataFeature);
						}					
					}else {
						injectSimpleObject(val, dataFeature);
					}
				}
			}
		} catch (Exception e) {
			logger.error("数据注入-深度遍历注入异常", e);
			throw new BaseException("数据注入-深度遍历注入异常");
		}
	}

	private static void invoke(Object object, DataInjectConfig injectConfig) {

		try {
			Object resultData = queryData(object, injectConfig);
			if (resultData == null) {
				return;
			}
			injectConfig.targetSetter().invoke(object, resultData);
		} catch (Exception e) {
			logger.error("数据注入异常", e);
			throw new BusinessException("数据注入异常", e);
		}
	}
	
	/**
	 * 查询数据
	 * @param object
	 * @param injectConfig
	 * @return
	 * @throws Exception
	 */
	private static Object queryData(Object object, DataInjectConfig injectConfig) throws Exception {
		DataInjectService service = SpringUtils.getBean(DataInjectService.class);
		if(service == null) {
			throw new BusinessException("未获取到DataInject实例");
		}
		
		QueryEntry queryEntry = createQuerySql(object, injectConfig);
		if(queryEntry.isUseCache()) {
			Object data = RedisUtil.get(queryEntry.getRedisKey());
			if(data != null) {
				return data;
			}
		}
		
		String sql = queryEntry.getSql();
		Object result;
		if(injectConfig.getResultType() == ResultType.SINGLE) {
			sql += " limit 1";
			result = service.queryOne(sql);
		}else {
			sql = !sql.contains("limit")? sql + " limit 200":sql;
			result = service.queryList(sql);
		}
		result = convertResult(result, injectConfig);
		
		if(queryEntry.isUseCache()) {
			if(result != null) {
				RedisUtil.set(queryEntry.getRedisKey(), result, queryEntry.getExpire());
			}
		}
		return result;
	}
	
	/**
	 * 转换封装结果
	 * @param result
	 * @param injectConfig
	 * @return
	 */
	private static Object convertResult(Object result, DataInjectConfig injectConfig) {
		if(injectConfig.getDataType() == Map.class) {
			return result;
		}
		if(injectConfig.getResultType() == ResultType.SINGLE) {
			return JsonUtil.toJavaObjectWithSnake(result, injectConfig.getDataType());
		}
		return JsonUtil.toJavaListWithSnake((List<?>)result, injectConfig.getDataType());
	}
	
	/**
	 * 创建查询SQL
	 * @param object
	 * @param injectConfig
	 * @return
	 * @throws Exception
	 */
	private static QueryEntry createQuerySql(Object object, DataInjectConfig injectConfig) throws Exception {
		
		Duration expireDuration = null;
		if(StringUtils.isNotBlank(injectConfig.getExpire()) && !DataInjectConfig.EXPIRE_AUTO.equalsIgnoreCase(injectConfig.getExpire())) {
			expireDuration = DateUtil.parseTtl(injectConfig.getExpire());
		}
		QueryEntry queryEntry = new QueryEntry().setUseCache(injectConfig.isUseCache());
		
		if(StringUtils.isNotBlank(injectConfig.getCustomSql())) {
			String sql = SqlUtil.fillTemplate(injectConfig.getCustomSql(), injectConfig.getVarList(), getValues(object, injectConfig.getVarFieldGetterList()));
			queryEntry.setSql(sql);
			
			if(queryEntry.isUseCache()) {
				queryEntry.setRedisKey(String.format(DataInjectConstant.DATA_INJECT_KEY, injectConfig.getTableName(), sql));
				queryEntry.setExpire(expireDuration!=null?expireDuration.getSeconds():DataInjectConstant.DATA_INJECT_EXPIRE_CUSTOM);
			}
			return queryEntry;
		}
		
		//单表查询
		StringBuilder sqlBuilder = new StringBuilder(MessageFormat.format("select {0} from {1} where ", 
				injectConfig.getListField(), injectConfig.getTableName()));
		boolean hasAutoCondistion = false;
		if(CollectionUtil.isNotEmpty(injectConfig.origGetterList()) && CollectionUtil.isNotEmpty(injectConfig.columnList())) {
			Object value;
			int i = 0;
			List<String> columns = injectConfig.columnList();
			for (Method origGetter : injectConfig.origGetterList()) {
				value = origGetter.invoke(object);
				appendSql(sqlBuilder, columns.get(i), value, i==0);
				i++;
			}
			hasAutoCondistion = true;
		}
		
		// 有SQL条件的
		String conditionSql = SqlUtil.fillTemplate(injectConfig.getConditionSql(), injectConfig.getVarList(), 
				getValues(object, injectConfig.getVarFieldGetterList()));
		conditionSql = StringUtils.trim(conditionSql);
		if(StringUtils.isNotBlank(conditionSql)) {
			sqlBuilder.append(" ");
			if(hasAutoCondistion) {
				if(!conditionSql.startsWith("and") && !conditionSql.startsWith("or")) {
					sqlBuilder.append("and ");
				}
				sqlBuilder.append(conditionSql);
			}else {
				if(conditionSql.startsWith("and")) {
					sqlBuilder.append(conditionSql.replaceFirst("and", ""));
				}else if(conditionSql.startsWith("or")) {
					sqlBuilder.append(conditionSql.replaceFirst("or", ""));
				}else {
					sqlBuilder.append(conditionSql);
				}
			}
		}
		String sql = sqlBuilder.toString();
		
		if(queryEntry.isUseCache()) {
			queryEntry.setRedisKey(String.format(DataInjectConstant.DATA_INJECT_KEY, injectConfig.getTableName(), sql));
			queryEntry.setExpire(expireDuration!=null?expireDuration.getSeconds():DataInjectConstant.DATA_INJECT_EXPIRE_CONDITION);
		}
		return queryEntry.setSql(sql);
	}
	
	private static void appendSql(StringBuilder sqlBuilder, String colunm, Object value, boolean isFirst) {
		if(!isFirst) {
			sqlBuilder.append(" and ");
		}
		sqlBuilder.append(colunm);
		if(value == null) {
			sqlBuilder.append(" is null");
		}else if(value instanceof String) {
			sqlBuilder.append(" = '").append(value).append("'");
		}else {
			sqlBuilder.append(" = ").append(value);
		}
	}
	
	/**
	 * 获取SQL模板对应的变量值
	 * @param object
	 * @param getterList
	 * @return
	 * @throws Exception
	 */
	private static Object[] getValues(Object object, List<Method> getterList) throws Exception {
		if (CollectionUtil.isEmpty(getterList)) {
			return null;
		}
		Object[] values = new Object[getterList.size()];
		int i = 0;
		Object v;
		for (Method getterMethod : getterList) {
			v = getterMethod.invoke(object);
			values[i++] = v == null ? "" : v;
		}
		return values;
	}

	/**
	 * 获取注入配置
	 * @param clazz
	 * @return
	 */
	private static List<DataInjectConfig> getDataInjectConfig(Class<?> clazz, DataFeature dataFeature, List<DataDeepConfig> deepList) {

		List<Field> fieldList = BeanUtil.getSimpleField(clazz, true);
    	if(dataFeature.deep() && deepList != null) {
    		deepList.addAll(getFieldDeepConfig(fieldList));
    	}
    	
    	Map<Field, DataInject> fieldInjectData = getFieldAnnotation(fieldList);
		if (CollectionUtil.isEmpty(fieldInjectData)) {
			return Collections.emptyList();
		}

		List<DataInjectConfig> injectConfigList = new ArrayList<>();
		for (Entry<Field, DataInject> entry : fieldInjectData.entrySet()) {
			injectConfigList.add(getFieldInfo(clazz, new DataInjectConfig(entry.getKey().getName(), entry.getValue())));
		}
		return injectConfigList;
	}
	
	private static Map<Field, DataInject> getFieldAnnotation(List<Field> fieldList) {
		Map<Field, DataInject> fieldData = new LinkedHashMap<>();
		DataInject a;
		for (Field field : fieldList) {
			a = BeanUtil.getFieldAnnotation(field, DataInject.class, BeanUtil.METHOD_TYPE_GETTER);
			if (a != null && (StringUtils.isNotBlank(a.tableName()) || StringUtils.isNotBlank(a.customSql()))) {
				fieldData.put(field, a);
			}
		}
		return fieldData;
	}
	
	private static List<DataDeepConfig> getFieldDeepConfig(List<Field> fieldList) {
    	List<DataDeepConfig> deepList = new ArrayList<>();
    	if(CollectionUtil.isEmpty(fieldList)) {
    		return deepList;
    	}
    	
    	DataDeepConfig dictDeepConfig;
		for (Field field : fieldList) {
			if(DataInjectible.class.isAssignableFrom(field.getType()) || List.class.isAssignableFrom(field.getType())) {
				dictDeepConfig = new DataDeepConfig();
				if(List.class.isAssignableFrom(field.getType())) {
					Class<?>[] fieldTypes = BeanUtil.getActualType(field.getGenericType());
					if(CollectionUtil.isEmpty(fieldTypes) || !DataInjectible.class.isAssignableFrom(fieldTypes[0])) {
						continue;
					}
					dictDeepConfig.setList(true);
					dictDeepConfig.setFieldType(fieldTypes[0]);
				}else {
					dictDeepConfig.setList(false);
					dictDeepConfig.setFieldType(field.getType());
				}
				dictDeepConfig.setFieldName(field.getName());
				dictDeepConfig.setFieldGetter(BeanUtil.getGetterMethod(field.getDeclaringClass(), field.getName()));
				deepList.add(dictDeepConfig);
			}
		}
		return deepList;
    }
	
	/**
	 * 拆分源字段并获取源字段的getter
	 * @param clazz
	 * @param injectConfig
	 */
	private static void splitOrigField(Class<?> clazz, DataInjectConfig injectConfig) {
		
		if(StringUtils.isBlank(injectConfig.getOrigField())) {
			return ;
		}
		String[] fields = injectConfig.getOrigField().split(",");
		String[] fssTmp = null;
		List<String> origFieldList = new ArrayList<>();
		List<String> columnList = new ArrayList<>();
		for (String field : fields) {
			if(StringUtils.isBlank(field)) {
				continue;
			}
			
			if (field.contains("=>") && (fssTmp = field.split("=>")).length > 1) {
				origFieldList.add(fssTmp[0]);
				columnList.add(fssTmp[1]);
			} else {
				origFieldList.add(field);
				String colunm = TableInfoUtil.getColumn(clazz, field);
				if(StringUtils.isBlank(colunm)) {
					logger.error("数据注入时检查到该实体类不存在指定的字段，class:{}, fieldName:{}", clazz.getName(), field);
					throw new BaseException("不存在的字段："+field);
				}
				columnList.add(colunm);
			}
		}
		injectConfig.setOrigFieldList(origFieldList);
		injectConfig.setColumnList(columnList);
		
		// origGetter
		List<Method> origGetterList = new ArrayList<>();
		for (String origField : origFieldList) {
			origGetterList.add(BeanUtil.getGetterMethod(clazz, origField));
		};
		injectConfig.setOrigGetterList(origGetterList);
	}
	
	/**
	 * 获取注解所在字段的反射信息，以及SQL模板中变量的反射信息
	 * @param clazz
	 * @param injectConfig
	 * @return
	 */
	private static DataInjectConfig getFieldInfo(Class<?> clazz, DataInjectConfig injectConfig) {

		//没有sql片段的时候，需要从源字段中获取值
		if(StringUtils.isBlank(injectConfig.getCustomSql())) {
			//拆分源字段并获取源字段的getter
			splitOrigField(clazz, injectConfig);
		}

		// TargetSetter
		injectConfig.setTargetSetter(BeanUtil.getSetterMethod(clazz, injectConfig.getFieldName()));

		// 有ConditionSQL片段
		if (CollectionUtil.isNotEmpty(injectConfig.getVarList())) {
			List<Method> varFieldGetterList = new ArrayList<>();
			for (String var : injectConfig.getVarList()) {
				varFieldGetterList.add(BeanUtil.getGetterMethod(clazz, StringUtils.replace(var, SqlUtil.DATA_TYPE_SUFFIX_NUM, "")));
			}
			injectConfig.setVarFieldGetterList(varFieldGetterList);
		}
		
		Class<?> resultTypeClass = injectConfig.targetSetter().getParameterTypes()[0];
//		Field targetField = BeanUtil.getField(clazz, injectConfig.getTargetField());
//		Class<?> resultTypeClass = targetField.getType();
		//自动识别结果类型
		if(injectConfig.getResultType() == null || injectConfig.getResultType() == ResultType.AUTO) {
			if(DataInjectible.class.isAssignableFrom(resultTypeClass)) {
				injectConfig.setResultType(ResultType.SINGLE);
			}else if(isBasicType(resultTypeClass)) {
				injectConfig.setResultType(ResultType.SINGLE);
			}else {
				injectConfig.setResultType(ResultType.LIST);
			}
		}
		
		//自动识别字段数据类型
		if(injectConfig.getDataType() == null || injectConfig.getDataType() == Auto.class) {
			if(DataInjectible.class.isAssignableFrom(resultTypeClass)) {
				injectConfig.setDataType(resultTypeClass);
			}else if(isBasicType(resultTypeClass)) {
				injectConfig.setDataType(resultTypeClass == Object.class ? Map.class : resultTypeClass);
			}else {
				if(!List.class.isAssignableFrom(resultTypeClass)) {
					throw new BaseException("不支持的数据注入类型, typeClass:" + resultTypeClass.getName());
				}
				
				Type genericType = injectConfig.targetSetter().getGenericParameterTypes()[0];
				Class<?>[] fieldTypes = BeanUtil.getActualType(genericType);
				if(CollectionUtil.isEmpty(fieldTypes)) {
	            	injectConfig.setDataType(Map.class);
	            }else {
	            	Class<?> fieldTypeClass = fieldTypes[0];
	            	if(DataInjectible.class.isAssignableFrom(fieldTypeClass)) {
	    				injectConfig.setDataType(fieldTypeClass);
	    			}else if(isBasicType(fieldTypeClass)) {
	    				injectConfig.setDataType(fieldTypeClass == Object.class ? Map.class : fieldTypeClass);
	    			}else {
	    				throw new BaseException("不支持的数据注入类型, typeClass:" + fieldTypeClass.getName());
	    			}
	            }
			}
		}

		return injectConfig;
	}
	
	private static boolean isBasicType(Class<?> typeClass) {
		return typeClass == Object.class || typeClass == Map.class ;
//				|| typeClass == String.class || typeClass == Long.class 
//				|| typeClass == Integer.class || typeClass == Short.class || typeClass == Double.class || typeClass == Float.class 
//				|| typeClass == BigDecimal.class || typeClass == Date.class;
	}

}
